import type { $CalendarPopupSelected } from './mxp-calendar-popup.vue';
import { type Solar2lunar, solar2lunar } from '../mxp-tool/sdk/solarlunar-es';

export type $Date = {
  fullDate: string;
  year: number;
  month: number | string;
  date: number | string;
  day: number;
};

export type $NewDate = {
  fullDate?: string;
  year?: number;
  date: number;
  type?: 'month' | 'week';
  mode?: 'multiple' | 'range' | 'single';
  multiples?: boolean;
  rangeMultiple?: boolean;
  beforeRange?: boolean;
  afterRange?: boolean;
  month: number | string;
  lunar: Solar2lunar;
  disable: boolean;
  isDay?: boolean;
  extraInfo?: $CalendarPopupSelected;
  empty?: boolean;
};

type $Op = {
  selected: $CalendarPopupSelected[];
  startDate: string;
  endDate: string;
  mode: 'multiple' | 'range' | 'single';
  monthShowCurrentMonth: boolean;
  rangeEndRepick: boolean;
  rangeSameDay: boolean;
  rangeHaveDisableTruncation: boolean;
  type: 'month' | 'week';
  foldStatus: boolean;
  startWeek: 'mon' | 'sun';
};

class Calendar {
  public date: $Date;
  public selected: $CalendarPopupSelected[];
  public selectDate: $Date | undefined;
  public startDate: string;
  public endDate: string;
  public mode: 'multiple' | 'range' | 'single';
  public type: 'month' | 'week';
  public foldStatus: boolean;
  public rangeEndRepick: boolean;
  public rangeSameDay: boolean;
  public rangeHaveDisableTruncation: boolean;
  public monthShowCurrentMonth: boolean;
  public startWeek: 'mon' | 'sun';
  public weeks: Record<number, $NewDate[]>;
  public rangeStatus: { before: string; after: string; data: string[] } = { before: '', after: '', data: [] };
  public multiple: string[] = [];
  public canlender: $NewDate[] = [];

  public constructor(
    {
      selected,
      startDate,
      endDate,
      mode,
      monthShowCurrentMonth,
      rangeEndRepick,
      rangeSameDay,
      rangeHaveDisableTruncation,
      type,
      foldStatus,
      startWeek,
    } = {} as $Op
  ) {
    // 当前日期
    this.date = this.getDate(new Date()); // 当前初入日期
    // 打点信息
    this.selected = selected || [];
    // 范围开始
    this.startDate = startDate;
    // 范围结束
    this.endDate = endDate;
    // 日历以周几开始
    this.startWeek = startWeek;
    // 日期选择类型
    this.mode = mode;
    // 日历类型
    this.type = type;
    // 折叠状态
    this.foldStatus = foldStatus;
    // 允许范围内重选结束日期
    this.rangeEndRepick = rangeEndRepick;
    // 允许日期选择范围起始日期为同一天
    this.rangeSameDay = rangeSameDay;
    // 日期选择范围内遇到打点禁用日期是否截断
    this.rangeHaveDisableTruncation = rangeHaveDisableTruncation;
    // 每月是否仅显示当月的数据
    this.monthShowCurrentMonth = monthShowCurrentMonth;
    // 清理多选状态
    this.cleanRange();
    // 每周日期
    this.weeks = {} as Record<number, $NewDate[]>;
    // 多个日期
    this.multiple = [];
  }
  /**
   * 设置日期
   */
  public setDate(date: Date | number | string) {
    this.selectDate = this.getDate(date);
    this._getWeek(this.selectDate.fullDate);
  }

  /**
   * 清除范围
   */
  public cleanRange() {
    this.rangeStatus = { before: '', after: '', data: [] };
  }

  /**
   * 清除多选
   */
  public cleanMultiple() {
    this.multiple = [];
  }

  /**
   * 重置开始日期
   */
  public resetSatrtDate(startDate: string) {
    // 范围开始
    this.startDate = startDate;
  }

  /**
   * 重置结束日期
   */
  public resetEndDate(endDate: string) {
    // 范围结束
    this.endDate = endDate;
  }

  /**
   * 重置是否每月仅显示当月数据
   */
  public resetMonthShowCurrentMonth(show: boolean) {
    this.monthShowCurrentMonth = show;
  }

  // 重置允许范围内重选结束日期
  public resetRangeEndRepick(val: boolean) {
    this.rangeEndRepick = val;
  }

  // 重置允许日期范围选择起始日期为同一天
  public resetRangeSameDay(val: boolean) {
    this.rangeSameDay = val;
  }

  // 重置范围内遇到打点禁用日期是否截断
  public resetRangeHaveDisableTruncation(val: boolean) {
    this.rangeHaveDisableTruncation = val;
  }

  // 重置日期选择模式
  public resetMode(val: 'multiple' | 'range' | 'single') {
    this.mode = val;
  }

  // 重置折叠状态
  public resetFoldStatus(state: boolean) {
    this.foldStatus = state;
  }

  // 重置日历以周几开始
  public resetStartWeek(val: 'mon' | 'sun') {
    this.startWeek = val;
  }
  /**
   * 获取任意时间
   */
  public getDate(_date: Date | number | string, AddDayCount = 0, str = 'day'): $Date {
    let date = _date;
    if (!date) {
      date = new Date();
    }
    if (typeof date === 'string') {
      date = date.replace(/-/g, '/');
    }
    const dd = new Date(date);
    // eslint-disable-next-line default-case
    switch (str) {
      case 'day':
        dd.setDate(dd.getDate() + AddDayCount); // 获取AddDayCount天后的日期
        break;
      case 'month':
        if (dd.getDate() === 31 && AddDayCount > 0) {
          dd.setDate(dd.getDate() + AddDayCount);
        } else {
          const preMonth = dd.getMonth();
          dd.setMonth(preMonth + AddDayCount); // 获取AddDayCount天后的日期
          const nextMonth = dd.getMonth();
          // 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
          if (AddDayCount < 0 && preMonth !== 0 && nextMonth - preMonth > AddDayCount) {
            dd.setMonth(nextMonth + (nextMonth - preMonth + AddDayCount));
          }
          // 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题
          if (AddDayCount > 0 && nextMonth - preMonth > AddDayCount) {
            dd.setMonth(nextMonth - (nextMonth - preMonth - AddDayCount));
          }
        }
        break;
      case 'week':
        dd.setDate(dd.getDate() + AddDayCount * 7);
        break;
      case 'year':
        dd.setFullYear(dd.getFullYear() + AddDayCount); // 获取AddDayCount天后的日期
        break;
    }
    const y = dd.getFullYear();
    const m = dd.getMonth() + 1 < 10 ? `0${dd.getMonth() + 1}` : dd.getMonth() + 1; // 获取当前月份的日期，不足10补0
    const d = dd.getDate() < 10 ? `0${dd.getDate()}` : dd.getDate(); // 获取当前几号，不足10补0
    return {
      fullDate: `${y}-${m}-${d}`,
      year: y,
      month: m,
      date: d,
      /** 星期几 */
      day: dd.getDay(),
    };
  }

  /**
   * 获取当前日期详情
   */
  public getInfo(_date: string) {
    const date: Date | string = _date ? _date : new Date();
    const dateInfo = this.canlender.find((item) => 'fullDate' in item && item.fullDate === this.getDate(date).fullDate);
    return dateInfo;
  }

  /**
   * 比较时间大小
   */
  public dateCompare(startDate: string, endDate: string) {
    // 计算截止时间
    const start = new Date(startDate.replace(/-/g, '/'));
    // 计算详细项的截止时间
    const end = new Date(endDate.replace(/-/g, '/'));
    return start.getTime() <= end.getTime();
  }

  /**
   * 比较时间是否相等
   */
  public dateEqual(startDate: string, endDate: string) {
    // 计算截止时间
    const start = new Date(startDate.replace(/-/g, '/'));
    // 计算详细项的截止时间
    const end = new Date(endDate.replace(/-/g, '/'));
    return start.getTime() === end.getTime();
  }

  /**
   * 获取日期范围内所有日期
   */
  public getDateAll(begin: string, end: string): string[] {
    // 找出所有打点中已禁用的部分 不让其被添加在日期选择范围内
    const disableList = this.selected.filter((item) => item.date && item.disable).map((item) => item.date);

    const arr = [];
    const ab = begin.split('-');
    const ae = end.split('-');
    const db = new Date();
    db.setFullYear(Number(ab[0]!), Number(ab[1]!) - 1, Number(ab[2]));
    const de = new Date();
    de.setFullYear(Number(ae[0]), Number(ae[1]) - 1, Number(ae[2]));
    const wuxDb = db.getTime() - 24 * 60 * 60 * 1000;
    const wuxDe = de.getTime() - 24 * 60 * 60 * 1000;
    for (let k = wuxDb; k <= wuxDe; ) {
      k += 24 * 60 * 60 * 1000;
      const { fullDate } = this.getDate(new Date(Math.floor(k)));
      // 如果要在选择范围内截断日期
      if (this.rangeHaveDisableTruncation) {
        // 如果不在打点禁止列表中
        if (disableList.includes(fullDate)) {
          return arr;
        }
        arr.push(fullDate);
      } else {
        if (!disableList.includes(fullDate)) {
          arr.push(fullDate);
        }
      }
    }
    return arr;
  }
  /**
   * 计算阴历日期显示
   */
  public getlunar(year: number, month: number, date: number) {
    return solar2lunar(year, month, date) as Solar2lunar;
  }
  /**
   * 设置打点
   */
  public setSelectInfo(data: string, value: $CalendarPopupSelected[]) {
    this.selected = value;
    this._getWeek(data);
  }

  /**
   * 设置范围
   */
  public setRange(fullDate: string) {
    const { before, after } = this.rangeStatus;

    // 非范围选择不再执行
    if (this.mode !== 'range') {
      return;
    }

    // 判断目前的日期 是否 比before日期小或者等于before日期 如果为true就要重置
    let reset = this.dateCompare(fullDate, before);
    // 如果日期选择范围允许为同一天 且 目前是需要重置的
    if (this.rangeSameDay && before && reset) {
      // 判断是否需要相等 如果 不相等 则 重置 如果相等 则不重置
      reset = !this.dateEqual(fullDate, before);
    }

    if (((before && after) || reset) && (!this.rangeEndRepick || (this.rangeEndRepick && this.rangeStatus.data.indexOf(fullDate) === -1))) {
      this.rangeStatus.before = fullDate;
      this.rangeStatus.after = '';
      this.rangeStatus.data = [];
    } else {
      if (!before) {
        this.rangeStatus.before = fullDate;
      } else {
        if (this.dateCompare(this.rangeStatus.before, fullDate)) {
          this.rangeStatus.data = this.getDateAll(this.rangeStatus.before, fullDate);
        } else {
          this.rangeStatus.data = this.getDateAll(fullDate, this.rangeStatus.before);
        }
        this.rangeStatus.after = this.rangeStatus.data[this.rangeStatus.data.length - 1]!;
      }
    }
    this._getWeek(fullDate);
  }

  /**
   * 设置多选
   */
  public setMultiple(fullDate: string) {
    // 非多选不再执行
    if (this.mode !== 'multiple') {
      return;
    }
    // 检查是否已经多选
    const index = this.multiple.findIndex((item) => this.dateEqual(fullDate, item));
    if (index < 0) {
      this.multiple.push(fullDate);
      this.setDate(fullDate);
    } else {
      this.multiple = this.multiple.filter((_item, i) => i !== index);
    }
    this._getWeek(fullDate);
  }

  /**
   * 获取每周数据
   */
  public _getWeek(dateData: string, useWeeks = true) {
    const { year, month } = this.getDate(dateData);

    const weeks = {} as Record<number, $NewDate[]>;
    // 日历数据
    let canlender: $NewDate[] = [];

    if (this.foldStatus) {
      // 默认以周末开始
      let firstDay = new Date(year, Number(month) - 1, 1).getDay();
      // 如果以周一开始
      if (this.startWeek === 'mon') {
        firstDay = firstDay === 0 ? 6 : firstDay - 1;
      }
      const currentDay = new Date(year, Number(month), 0).getDate();
      // 日期数据
      const dates = {
        nextMonthDays: [] as $NewDate[],
        lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
        currentMonthDys: this._currentMonthDays(currentDay, this.getDate(dateData)), // 本月天数
        weeks: [],
      };
      // 下月开始几天
      const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length);
      dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData));

      // 如果仅显示当月
      if (this.monthShowCurrentMonth) {
        const last: $NewDate[] = dates.lastMonthDays.map(() => {
          return { empty: true, lunar: {} } as $NewDate;
        });
        const next: $NewDate[] = dates.nextMonthDays.map(() => {
          return { empty: true, lunar: {} } as $NewDate;
        });
        // 日历数据
        canlender = canlender.concat(last, dates.currentMonthDys, next);
      } else {
        // 拼接数组  上个月开始几天 + 本月天数+ 下个月开始几天
        canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays);
      }
    } else {
      canlender = this._getWeekDays(dateData);
    }

    for (let i = 0; i < canlender.length; i++) {
      if (i % 7 === 0) {
        weeks[Math.floor(i / 7)] = new Array(7);
      }
      weeks[Math.floor(i / 7)]![i % 7] = canlender[i] || ({} as $NewDate);
    }

    if (useWeeks) {
      this.canlender = canlender;
      this.weeks = weeks;
    }
    return weeks;
  }

  /**
   * 创建本月某一天的信息
   */
  private _createCurrentDay(nowDate: string, full: $Date, date: number): $NewDate {
    // 是否今天
    const isDay = this.date.fullDate === nowDate;
    // 获取打点信息
    const info = this.selected && this.selected.find((item) => this.dateEqual(nowDate, item.date));

    // 日期禁用
    let disableBefore = true;
    let disableAfter = true;
    if (this.startDate) {
      disableBefore = this.dateCompare(this.startDate, nowDate);
    }

    if (this.endDate) {
      disableAfter = this.dateCompare(nowDate, this.endDate);
    }

    // 范围选择模式
    const ranges = this.rangeStatus!.data;
    let checked = false;
    if (this.mode === 'range') {
      checked = ranges.findIndex((item) => this.dateEqual(item, nowDate)) !== -1;
    }

    // 多日期选择模式
    const multiples = this.multiple;
    let multiplesChecked = false;
    if (this.mode === 'multiple') {
      multiplesChecked = multiples.findIndex((item) => this.dateEqual(item, nowDate)) !== -1;
    }

    const data: $NewDate = {
      fullDate: nowDate,
      year: full.year,
      date,
      type: this.type,
      mode: this.mode,
      multiples: this.mode === 'multiple' ? multiplesChecked : false,
      rangeMultiple: this.mode === 'range' ? checked : false,
      beforeRange: this.dateEqual(this.rangeStatus.before, nowDate),
      afterRange: this.dateEqual(this.rangeStatus.after, nowDate),
      month: full.month,
      lunar: this.getlunar(full.year, Number(full.month), date),
      disable: !(disableBefore && disableAfter),
      isDay,
    };

    if (info) {
      data.extraInfo = info;
      data.disable = info.disable || false;
    }

    return data;
  }

  /**
   * 获取上月剩余天数
   */
  private _getLastMonthDays(firstDay: number, full: $Date): $NewDate[] {
    const month = Number(full.month) - 1;
    const dateArr = [];
    for (let i = firstDay; i > 0; i--) {
      const beforeDate = new Date(full.year, month, -i + 1).getDate();
      dateArr.push({
        date: beforeDate,
        month,
        year: full.year,
        lunar: this.getlunar(full.year, month, beforeDate),
        disable: true,
      });
    }
    return dateArr;
  }

  /**
   * 获取本月天数
   */
  private _currentMonthDays(dateData: number, full: $Date): $NewDate[] {
    const dateArr: $NewDate[] = [];
    for (let i = 1; i <= dateData; i++) {
      const nowDate = `${full.year}-${full.month}-${i < 10 ? `0${i}` : i}`;
      dateArr.push(this._createCurrentDay(nowDate, full, i));
    }
    return dateArr;
  }
  /**
   * 获取下月天数
   */
  private _getNextMonthDays(surplus: number, full: $Date): $NewDate[] {
    const dateArr: $NewDate[] = [];
    for (let i = 1; i < surplus + 1; i++) {
      dateArr.push({
        date: i,
        month: Number(full.month) + 1,
        lunar: this.getlunar(full.year, Number(full.month) + 1, i),
        disable: true,
      });
    }
    return dateArr;
  }
  /**
   * 获取任意日期的一周
   */
  private _getWeekDays(dateData: string): $NewDate[] {
    const dateArr: $NewDate[] = [];
    const oneDayTime = 1000 * 60 * 60 * 24;
    const today = new Date(dateData);
    // 获取这个日期是星期几
    let todayDay: number | undefined = undefined;
    let startDate: Date | undefined = undefined;
    // 如果日历以周一开始
    if (this.startWeek === 'mon') {
      todayDay = today.getDay() || 7;
      startDate = new Date(today.getTime() - oneDayTime * (todayDay - 1));
    } else {
      todayDay = today.getDay();
      startDate = new Date(today.getTime() - oneDayTime * todayDay);
    }

    for (let i = 0; i < 7; i++) {
      const temp = new Date(startDate.getTime() + i * oneDayTime);
      const newDate = this.getDate(`${temp.getFullYear()}-${temp.getMonth() + 1}-${temp.getDate()}`);
      dateArr.push(this._createCurrentDay(newDate.fullDate, newDate, Number(newDate.date)));
    }

    return dateArr;
  }
}

export default Calendar;
